package com.exam.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.exam.exception.BusinessException;
import com.exam.exception.CommonErrorCode;
import com.exam.utils.SignUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.TreeMap;

import static com.exam.utils.AnnotationUtils.LogAndInvokeTargetMethod;

@Aspect
@Component
@RequiredArgsConstructor
public class ApiSignAspect {

    ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 对目标方法进行环绕通知
     *
     * @param joinPoint 切面连接点对象
     * @return 目标方法执行结果
     * @throws BusinessException 当签名验证失败时抛出此异常
     */
    @SneakyThrows
    @Around("execution(public * com.exam.controller.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) {
        // 获取当前请求的RequestAttributes对象
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        // 从RequestAttributes对象中获取HttpServletRequest对象
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        // 获取目标方法的对象
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Class<?> clazz = joinPoint.getTarget().getClass();
        Logger logger = LoggerFactory.getLogger(clazz);

        // 获取请求的URL
        String requestURL = request.getRequestURL().toString();
        // 如果请求的URL包含"util"或"common"，则直接执行目标方法并记录日志
        if (requestURL.contains("util") || requestURL.contains("common")) {
            return LogAndInvokeTargetMethod(joinPoint, logger, String.format("%s中的%s方法", clazz.getName(), method.getName()), String.format("%s中的%s方法执行结束", clazz.getName(), method.getName()));
        }

        // 获取请求头中的签名、时间戳、随机数和查询字符串
        String sign = Optional.ofNullable(request.getHeader("sign")).orElse("");
        String timestamp = Optional.ofNullable(request.getHeader("x-timestamp")).orElse("");
        String nonce = Optional.ofNullable(request.getHeader("x-nonce")).orElse("");
        String queryString = Optional.ofNullable(request.getQueryString()).orElse("");
        String bodyString = Optional.ofNullable(request.getHeader("body-string")).orElse("");

        // 将参数按照字母顺序排序，并转换为JSON字符串
        TreeMap<String, Object> sortedParams = new TreeMap<>();
        sortedParams.put("x-timestamp", timestamp);
        sortedParams.put("x-nonce", nonce);
        sortedParams.put("query-string", queryString);
        sortedParams.put("body-string", bodyString);
        String originalData = objectMapper.writeValueAsString(sortedParams);

        // 验证签名是否正确
        if (!SignUtils.verify(originalData, SignUtils.getPublicKey(SignUtils.PUBLIC_KEY), sign)) {
            // 如果签名验证失败，则记录错误信息并抛出异常
            logger.error("sign failed: \n" +
                            "front sign: {}\n" +
                            "backend sign: {}",
                    sign, SignUtils.sign(originalData, SignUtils.getPrivateKey(SignUtils.PRIVATE_KEY))
            );
            throw new BusinessException(CommonErrorCode.SIGNATURE_FAILED);
        }

        // 签名验证通过，执行目标方法并记录日志
        return LogAndInvokeTargetMethod(joinPoint, logger, String.format("%s中的%s方法", clazz.getName(), method.getName()), String.format("%s中的%s方法执行结束", clazz.getName(), method.getName()));
    }
}
